package com.kiki.oauth.config;

import com.kiki.oauth.convert.MyJwtAccessTokenConverter;
import com.kiki.oauth.enums.AuthTokenTypeEnum;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.token.DefaultAccessTokenConverter;
import org.springframework.security.oauth2.provider.token.DefaultUserAuthenticationConverter;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.*;
import org.springframework.security.oauth2.provider.token.store.redis.RedisTokenStore;
import org.springframework.util.StringUtils;

import javax.sql.DataSource;
import java.security.KeyPair;

@Slf4j
@EnableAuthorizationServer
@Configuration
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {

    private final String REDIS_STORE_PREFIX = "oauth:";

    @Autowired(required = false)
    private DataSource dataSource;

    @Autowired
    private UserDetailsService userDetailsService;

    @Autowired
    private SecurityProperties securityProperties;

    @Autowired
    private RedisConnectionFactory redisConnectionFactory;

    @Autowired
    private AuthenticationManager authenticationManager;

    /**
     * 配置授权服务器的校验策略
     */
    @Override
    public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
        // 配置 获取用于验签 token 的公钥 Key uri 的访问权限
        security.tokenKeyAccess("permitAll()")
                // 验证accessToken uri 的访问权限
                .checkTokenAccess("isAuthenticated()")
                //authorization_code模式需配置
                .allowFormAuthenticationForClients();
    }

    /**
     * 配置本授权服务器允许的客户端凭证信息（clientId | clientSecret）
     */
    @Override
    public void configure(ClientDetailsServiceConfigurer clients)
            throws Exception {
        clients.jdbc(dataSource);
    }

    /**
     * 配置端点信息
     */
    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) {
        // @formatter:off
        endpoints.authenticationManager(this.authenticationManager)
                .tokenStore(tokenStore())
                //刷新token时需要
                .userDetailsService(userDetailsService);

        if (AuthTokenTypeEnum.JWT.getType().equals(securityProperties.getType())) {
            endpoints.accessTokenConverter(jwtAccessTokenConverter());
        }
        // @formatter:on
    }

    @Bean
    public TokenStore tokenStore() {
        String tokenType = securityProperties.getType();
        if(StringUtils.isEmpty(tokenType)){
            //默认是jdbc类型
            tokenType = AuthTokenTypeEnum.JDBC.getType();
        }
        TokenStore tokenStore = null;
        if (AuthTokenTypeEnum.JWT.getType().equals(tokenType)) {
            tokenStore = new JwtTokenStore(jwtAccessTokenConverter());
        } else if (AuthTokenTypeEnum.JDBC.getType().equals(tokenType)) {
            tokenStore = new JdbcTokenStore(dataSource);
        } else if (AuthTokenTypeEnum.REDIS.getType().equals(tokenType)) {
            tokenStore = new RedisTokenStore(redisConnectionFactory);
            ((RedisTokenStore)tokenStore).setPrefix(REDIS_STORE_PREFIX);
        }else{
            throw new RuntimeException("error token store type "+tokenType);
        }
        return tokenStore;
    }

    //生成token的转换器，而token令牌默认是有签名的，且资源服务器需要验证这个签名。此处的加密及验签包括两种方式：
    //对称加密、非对称加密（公钥密钥）
    //对称加密需要授权服务器和资源服务器存储同一key值，而非对称加密可使用密钥加密，暴露公钥给资源服务器验签
    @Bean
    public JwtAccessTokenConverter jwtAccessTokenConverter() {
        MyJwtAccessTokenConverter jwtAccessTokenConverter = new MyJwtAccessTokenConverter();

        SecurityProperties.JwtProperties jwtProperties = securityProperties.getJwt();
        KeyPair keyPair = keyPair(jwtProperties, keyStoreKeyFactory(jwtProperties));
        jwtAccessTokenConverter.setKeyPair(keyPair);

        return jwtAccessTokenConverter;
    }

    private KeyPair keyPair(SecurityProperties.JwtProperties jwtProperties, KeyStoreKeyFactory keyStoreKeyFactory) {
        return keyStoreKeyFactory.getKeyPair(jwtProperties.getKeyPairAlias(), jwtProperties.getKeyPairPassword().toCharArray());
    }

    private KeyStoreKeyFactory keyStoreKeyFactory(SecurityProperties.JwtProperties jwtProperties) {
        return new KeyStoreKeyFactory(jwtProperties.getKeyStore(), jwtProperties.getKeyStorePassword().toCharArray());
    }
}
